#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <semaphore.h>
#include <cassert>
#include <pthread.h>
#define CAP 10
//循环队列
template<class T>
class RingQueue
{
private:
    void P(sem_t& sem)
    {
        int n = sem_wait(&sem);
        assert(0 == n);
    }

    void V(sem_t& sem)
    {
        int n = sem_post(&sem);
        assert(0 == n);
    }
public:
    RingQueue(int cap = CAP) :_queue(cap), _cap(cap)
    {

        //信号量初始化
        int n = sem_init(&_spaceSem, 0, cap);
        assert(n == 0);
        n = sem_init(&_dataSem, 0, 0);
        assert(n == 0);

        _productorStep = _consumerStep = 0;

        //互斥锁初始化
        pthread_mutex_init(&_productorMutex, nullptr);
        pthread_mutex_init(&_consumerMutex, nullptr);



    } 

    //生产
    void push(const T& in)
    {
        //申请信号量，如果没有申请到，将阻塞式等待，直到申请到为止
        P(_spaceSem);  //申请空间信号量

        //加锁 -> 先申请信号量再加锁，这样设计的好处：可以使一个线程在访问临界资源期间，
        //                                        别的线程也可以申请信号量，然后再阻塞式等待锁，让线程提前占有访问临界资源的权限
        //                                        大大的提高了效率                                      
        pthread_mutex_lock(&_productorMutex);

        //访问临界资源
        _queue[_productorStep++] = in;
        _productorStep %= _cap;

        //解锁
        pthread_mutex_unlock(&_productorMutex);

        //增加数据信号量
        V(_dataSem);

    }

    //消费
    void pop(T* out)
    {
        //申请信号量，如果没有申请到，将阻塞式等待，直到申请到为止
        P(_dataSem);  //申请数据信号量

        //加锁
        pthread_mutex_lock(&_consumerMutex);

        //访问临界资源
        *out = _queue[_consumerStep++];
        _consumerStep %= _cap;


        pthread_mutex_unlock(&_consumerMutex);

        //增加空间信号量
        V(_spaceSem);
    }
    ~RingQueue()
    {
        int n = sem_destroy(&_spaceSem);
        assert(n == 0);
        n = sem_destroy(&_dataSem);
        assert(n == 0);

        pthread_mutex_destroy(&_productorMutex);
        pthread_mutex_destroy(&_consumerMutex);

    }
private:
    std::vector<T> _queue; //用数组模拟循环队列
    int _cap; //循环队列长度
    sem_t _spaceSem; //空间信号量 -> 生产者关注的
    sem_t _dataSem; //数据信号量 -> 消费者关注的
    int _productorStep; //生产位置
    int _consumerStep;  //消费位置
    pthread_mutex_t _productorMutex; //生产者的锁
    pthread_mutex_t _consumerMutex; //消费者的锁
};
